﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using YasharEl.Infrastructure.ServiceBus.Unicast.Transport;
using YasharEl.Infrastructure.Diagnostics.Logging;
using System.Diagnostics.Contracts;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.ServiceBus.Unicast.Queuing;

namespace YasharEl.Infrastructure.ServiceBus.Unicast.FaultTolerance.Forwarder
{
    /// <summary>
    /// Implementation of IManageMessageFailures by forwarding messages
    /// using ISendMessages.
    /// </summary>
    public class FaultForwarder : IManageMessageFailures
    {
        #region Fields

        private Address localAddress;

        #endregion

        #region ReadOnly Fields

        private readonly ILogger<FaultForwarder> logger;
        private readonly IDependencyResolver resolver;

        #endregion

        #region Properties

        /// <summary>
        /// Endpoint to which message failures are forwarded
        /// </summary>
        public Address ErrorQueue { get; set; }

        #endregion

        #region Constructors

        public FaultForwarder(ILogger<FaultForwarder> logger, IDependencyResolver resolver)
        {
            Contract.Requires<ArgumentNullException>(logger != null, "logger could not null.");
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not null.");

            this.logger = logger;
            this.resolver = resolver;
        }

        #endregion

        #region IManageMessageFailures Members

        public void SerializationFailedForMessage(TransportMessage message, Exception e)
        {
            SendFailureMessage(message, e, "SerializationFailed");
        }

        public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e)
        {
            var id = message.Id;
            SendFailureMessage(message, e, "ProcessingFailed"); //overwrites message.Id
            message.Id = id;
        }

        public void Init(Address address)
        {
            localAddress = address;
        }

        #endregion

        #region Private Methods

        // Intentionally service-locate ISendMessages to avoid circular
        // resolution problem in the container
        private void SendFailureMessage(TransportMessage message, Exception e, string reason)
        {
            SetExceptionHeaders(message, e, reason);
            var sender = resolver.Resolve<ISendMessages>();
            try
            {
                sender.Send(message, ErrorQueue);
            }
            catch (Exception exception)
            {
                var qnfEx = exception as QueueNotFoundException;
                string errorMessage;
                if (qnfEx != null)
                    errorMessage = string.Format("Could not forward failed message to error queue '{0}' as it could not be found.", qnfEx.Queue);
                else
                    errorMessage = string.Format("Could not forward failed message to error queue, reason: {0}.", exception.ToString());
                logger.Fatal(exception, errorMessage);
                throw new InvalidOperationException(errorMessage, exception);
            }

        }

        private void SetExceptionHeaders(TransportMessage message, Exception e, string reason)
        {
            message.Headers["ServiceBus.ExceptionInfo.Reason"] = reason;
            message.Headers["ServiceBus.ExceptionInfo.ExceptionType"] = e.GetType().FullName;

            if (e.InnerException != null)
                message.Headers["ServiceBus.ExceptionInfo.InnerExceptionType"] = e.InnerException.GetType().FullName;

            message.Headers["ServiceBus.ExceptionInfo.HelpLink"] = e.HelpLink;
            message.Headers["ServiceBus.ExceptionInfo.Message"] = e.Message;
            message.Headers["ServiceBus.ExceptionInfo.Source"] = e.Source;
            message.Headers["ServiceBus.ExceptionInfo.StackTrace"] = e.StackTrace;

            message.Headers[TransportHeaderKeys.OriginalId] = message.Id;

            var failedQ = localAddress;

            message.Headers[FaultsHeaderKeys.FailedQ] = failedQ.ToString();
            message.Headers["ServiceBus.TimeOfFailure"] = DateTime.UtcNow.ToWireFormattedString();

        }

        #endregion
    }
}
